package astri.pdcontrol.client;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

import astri.pdcontrol.commclient.AstCommClient;
import astri.pdcontrol.commclient.AstCommClientConfig;
import astri.pdcontrol.commclient.AstCommClientListener;
import astri.pdcontrol.commclient.AstEndian;

/**
 * This is the Portable Device (PD) Control Client Side Java Class.
 * The class provides basic methods for portable devices to get connected and communicate with the corresponding server.
 *  
 * @author Echo, hspoon @ASTRI
 * @version 2.0
 */
public final class PDControlClient {
	final static boolean debug_on = true;
	
	public AstCommClient commClient = null;
	
	/**
	 * PDCotnrolClient Class Constructor with default client configuration.
	 * 
	 * kylee: should not expose AstCommClientConfig in this layer
	 */
	protected PDControlClient(AstCommClientConfig client_config)
	{
		DEBUG("Create PDControlClient");
		commClient = new AstCommClient(client_config);
	}
	
	/**
	 * Constructor with specified clientTimeout, serverTimeout and listener
	 * @param clientTimeout		Default timeout value for send timeout
	 * @param serverTimeout		Default Timeout from Server (in msec), e.g. read Timeout, connect Timeout
	 * @param listener			Event listener to handle event
	 */
	public PDControlClient(int serverTimeout, int clientTimeout, AstCommClientListener listener ) {
		DEBUG("Create PDControlClient");
		commClient = new AstCommClient(serverTimeout, clientTimeout, listener);
	}
	
	/**
	 * Constructor use default AstCommClientConfig with specified listener
	 * @param listener	Event listener to handle event
	 */
	public PDControlClient(AstCommClientListener listener ) {
		DEBUG("Create PDControlClient");
		commClient = new AstCommClient(listener);
	}
	
	/**
	 * To start service thread for PDControlClient
	 */
	public void startService() {
		commClient.open();
	}
	
	/**
	 * To free service thread for PDControlClient
	 */
	public void stopService() {
		commClient.close();
	}
	
	/**
	 * Start to discover server within duration of time (in msec)
	 * @param duration	How long discovery mode should do
	 * @throws IOException
	 */
	public void startDiscoveryServer(int duration) throws IOException {
		commClient.startDiscoveryRequest(duration);
	}
	
	/**
	 * Method to connect to a destination server.
	 * @param serverIP IP of the server
	 * @param serverPort Port of the server, e.g: 5555.
	 * @return true if successfully connected ; false otherwise.
	 */
	public boolean connectTo(String serverIP, int serverPort){ 
		return connectTo(serverIP, serverPort, commClient.config.serverTimeout);
	}
	
	/**
	 * Method to connect to a destination server.
	 * @param serverIP IP of the server
	 * @param serverPort Port of the server, e.g: 5555.
	 * @param timeout	connection Timeout in msec
	 * @return true if successfully connected ; false otherwise.
	 */
	public boolean connectTo(String serverIP, int serverPort, int timeout){ 

		DEBUG (String.format("connectTo() %s:%d", serverIP, serverPort));
		
		if (!commClient.connectTo(serverIP, serverPort, timeout))
		{
			DEBUG ("connectTo() error");
			return false;
		}
		
		return true;
	}

	/**
	 * Method to disconnect from the server.
	 */
	public void disconnect(){
		commClient.disconnect();
	}

	/**
	 * Method to indicate to server the START of a series of cursor positions in the future.
	 * @throws IOException 
	 */
	public void sendCursorStart() throws IOException{
		sendRawData(defines.MMI_PD_CONTROL_CS_ID_CURSOR_START, null);
	}
	
	/**
	 * Method to send out a single set of Relative Cursor Position Data.</br>
	 * Note: Must call sendCursorStart() before you start to send the cursor data and sendCursorFinish() after sent out the last set of data.
	 *  
	 * @param relX relative X-axis position
	 * @param relY relative Y-axis position
	 * @throws IOException 
	 * @see #sendCursorStart
	 * @see #sendCursorFinish
	 */
	public void sendCursorData(int relX, int relY) throws IOException{
		byte[] cursorPos = new byte[8];
		if(commClient.config.messageEndian == AstEndian.AST_LITTLEENDIAN) {
			AstEndian.int32ToByteLE(relX, cursorPos, 0);
			AstEndian.int32ToByteLE(relY, cursorPos, 4);
		}
		else {
			AstEndian.int32ToByteBE(relX, cursorPos, 0);
			AstEndian.int32ToByteBE(relY, cursorPos, 4);
		}
		
		sendRawData(defines.MMI_PD_CONTROL_CS_ID_CURSOR_DATA, cursorPos);	
	}

	/**
	 * Method to indicate to server the END of a series of cursor positions.
	 * @throws IOException 
	 */
	public void sendCursorFinish() throws IOException{
		sendRawData(defines.MMI_PD_CONTROL_CS_ID_CURSOR_STOP, null);
	}
	
	/**
	 * Method to indicate to server the START of a series of accelerometer data in the future.
	 * @throws IOException 
	 */
	public void sendAccStart() throws IOException{
		sendRawData(defines.MMI_PD_CONTROL_CS_ID_ACC_START, null);
	}
	
	/**
	 * Method to send out a single set of Accelerometer Data.</br>
	 * Note: Must call sendAccStart() before you start to send the cursor data and sendAccFinish() after sent out the last set of data.
	 *  
	 * @param accl_x accelerometer reading for X-axis
	 * @param accl_y accelerometer reading for Y-axis
	 * @param accl_z accelerometer reading for Z-axis
	 * @throws IOException 
	 * @see #sendAccStart
	 * @see #sendAccFinish
	 */
	public void sendAccData(int accl_x, int accl_y, int accl_z) throws IOException{
		byte[] accValue = new byte[12];
		if(commClient.config.messageEndian == AstEndian.AST_LITTLEENDIAN) {
			AstEndian.int32ToByteLE(accl_x,accValue,0);
			AstEndian.int32ToByteLE(accl_y,accValue,4);
			AstEndian.int32ToByteLE(accl_z,accValue,8);
		}
		else {
			AstEndian.int32ToByteBE(accl_x,accValue,0);
			AstEndian.int32ToByteBE(accl_y,accValue,4);
			AstEndian.int32ToByteBE(accl_z,accValue,8);
		}

		sendRawData(defines.MMI_PD_CONTROL_CS_ID_ACC_DATA, accValue);
	}
	
	/**
	 * Method to indicate to server the END of a series of accelerometer data.
	 * @throws IOException 
	 */
	public void sendAccFinish() throws IOException{
		sendRawData(defines.MMI_PD_CONTROL_CS_ID_ACC_STOP, null);
	}

	/**
	 * Method to indicate to server the START of a series of Text data in the future.
	 * @throws IOException 
	 */
	public void sendTxtStart() throws IOException{
		sendRawData(defines.MMI_PD_CONTROL_CS_ID_TEXT_START, null);
	}

	/**
	 * Method to send out a single set of Accelerometer Data.</br>
	 * Note: Must call sendTxtStart() before you start to send the text data and sendTxtFinish() after sent out the last set of data.
	 *  
	 * @param txt_content text data to send
	 * @throws IOException 
	 * @see #sendTxtStart
	 * @see #sendTxtFinish
	 */
	public void sendTxtData(String txt_content) throws IOException{
		//send body
		byte[] byteString = null;
		
		try {
			byteString = txt_content.getBytes("UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			INFO("Not to send it out");
			return;
		}
		sendRawData(defines.MMI_PD_CONTROL_CS_ID_TEXT_DATA, byteString);
	}
	
	/**
	 * Method to indicate to server the END of a series of Text data.
	 * @throws IOException 
	 */
	public void sendTxtFinish() throws IOException{
		sendRawData(defines.MMI_PD_CONTROL_CS_ID_TEXT_STOP, null);
	}

	/**
	 * send discrete single command
	 * @param key_code key code of the command, e.g.: QT Key Code.
	 * @throws IOException 
	 */
	public void sendQtKey(int key_code) throws IOException{
		byte[] data = new byte[8];
		if(commClient.config.messageEndian == AstEndian.AST_LITTLEENDIAN) {
			AstEndian.int32ToByteLE(defines.MMI_PD_CONTROL_CMD_TYPE_SOFTWARE_KB,data,0);
			AstEndian.int32ToByteLE(key_code,data,4);
		}
		else {
			AstEndian.int32ToByteBE(defines.MMI_PD_CONTROL_CMD_TYPE_SOFTWARE_KB,data,0);
			AstEndian.int32ToByteBE(key_code,data,4);
		}
		
		sendRawData(defines.MMI_PD_CONTROL_CS_ID_COMMAND, data);
	}

	/**
	 * send scroll bar data
	 * @param sliderScale the current position of the slider/total length of the slider (in %).
	 * @throws IOException 
	 */
	public void sendScrollData(int sliderScale) throws IOException{
		byte[] data = new byte[4];	
		if(commClient.config.messageEndian == AstEndian.AST_LITTLEENDIAN) {
			AstEndian.int32ToByteLE((int)sliderScale,data,0);
		}
		else {
			AstEndian.int32ToByteBE((int)sliderScale,data,0);
		}
		
		sendRawData(defines.MMI_PD_CONTROL_CS_ID_SCROLL_DATA, data);					
	}
	// Send  Data Methods END
	
	/**
	 * Send the encoded data to server with message id
	 * @param id	Message id
	 * @param data	Message data
	 */
	public void sendRawData(int id, byte[] data) throws IOException{
		sendRawData(id, data, commClient.config.clientTimeout);					
	}
	
	/**
	 * Send the encoded data to server with message id within timeout
	 * @param id	Message id
	 * @param data	Message data
	 * @param timeout	send timeout
	 */
	public void sendRawData(int id, byte[] data, int timeout) throws IOException{
		commClient.send(id, data, timeout);					
	}

	
	
	//////util function //////
	private static void DEBUG(String msg) {
		if(!debug_on) 
			return;
		StackTraceElement e = Thread.currentThread().getStackTrace()[2];
		String fullClassName = e.getClassName();            
        String methodName = e.getMethodName();
        int lineNumber = e.getLineNumber();
		System.out.println("["+fullClassName + "." + methodName + "():" + lineNumber+"]\t " + msg);
	}
	
	private static void INFO(String msg) {
		StackTraceElement e = Thread.currentThread().getStackTrace()[2];
		String fullClassName = e.getClassName();            
        String methodName = e.getMethodName();
        int lineNumber = e.getLineNumber();
		System.out.println("["+fullClassName + "." + methodName + "():" + lineNumber+"]\t " + msg);
	}
	
}
